//
//  Cache.swift
//  ExyteGrid
//
//  Created by Denis Obukhov on 14.08.2020.
//  Copyright © 2020 Exyte. All rights reserved.
//

#if os(iOS) || os(watchOS) || os(tvOS)

import UIKit.UIApplication

private class ObjectWrapper {
  let value: Any
  
  init(_ value: Any) {
    self.value = value
  }
}

private class KeyWrapper<KeyType: Hashable>: NSObject {
  let key: KeyType
  init(_ key: KeyType) {
    self.key = key
  }
  
  override var hash: Int {
    return key.hashValue
  }
  
  override func isEqual(_ object: Any?) -> Bool {
    guard let other = object as? KeyWrapper<KeyType> else {
      return false
    }
    return key == other.key
  }
}

class Cache<KeyType: Hashable, ObjectType> {
  private let cache: NSCache<KeyWrapper<KeyType>, ObjectWrapper> = NSCache()
  
  init(lowMemoryAware: Bool = true) {
    guard lowMemoryAware else { return }
    NotificationCenter.default.addObserver(
      self,
      selector: #selector(onLowMemory),
      name: UIApplication.didReceiveMemoryWarningNotification,
      object: nil)
  }
  
  @objc private func onLowMemory() {
    removeAllObjects()
  }
  
  var name: String {
    get { return cache.name }
    set { cache.name = newValue }
  }
  
  weak open var delegate: NSCacheDelegate? {
    get { return cache.delegate }
    set { cache.delegate = newValue }
  }
  
  func object(forKey key: KeyType) -> ObjectType? {
    return cache.object(forKey: KeyWrapper(key))?.value as? ObjectType
  }
  
  func setObject(_ obj: ObjectType, forKey key: KeyType) { // 0 cost
    return cache.setObject(ObjectWrapper(obj), forKey: KeyWrapper(key))
  }
  
  func setObject(_ obj: ObjectType, forKey key: KeyType, cost: Int) {
    return cache.setObject(ObjectWrapper(obj), forKey: KeyWrapper(key), cost: cost)
  }
  
  func removeObject(forKey key: KeyType) {
    return cache.removeObject(forKey: KeyWrapper(key))
  }
  
  func removeAllObjects() {
    return cache.removeAllObjects()
  }
  
  var totalCostLimit: Int {
    get { return cache.totalCostLimit }
    set { cache.totalCostLimit = newValue }
  }
  
  var countLimit: Int {
    get { return cache.countLimit }
    set { cache.countLimit = newValue }
  }
  
  var evictsObjectsWithDiscardedContent: Bool {
    get { return cache.evictsObjectsWithDiscardedContent }
    set { cache.evictsObjectsWithDiscardedContent = newValue }
  }
}

#endif
